#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
#include "log.hpp"

using namespace std;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USE_ERROR
};

using func_spread_t = function<void(int, struct sockaddr_in&, string)>;

class UdpServer
{
public:
    // UdpServer(string ip, uint16_t port) : _ip(ip), _port(port), _isrunning(false) //指定ip版本
    // {
    // }

    UdpServer(uint16_t port, func_spread_t funcspread) : _port(port), _funcspread(funcspread), _isrunning(false) // 不指定ip版本
    {
    }

    void initServer()
    {
        // 1、创建套接字
        // int socket(int domain, int type, int protocol)
        // domain：交流域，网络通信的套接字，网络类型(ipv4)，type:通讯协议(udp)，protocol写0
        // 返回值一个文件描述符指针，在文件系统中创建了一个文件结构体，因为linux一切皆文件
        // 套接字是全双工的，即支持写入又支持读取
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET也可以写成PF_INET,AF_INET是PF_INET定义的一个宏
        if (_sockfd == -1)
        {
            LOG(FATAL, "server: creates socket fatally, errno:%d, errmsg:%s", errno, strerror(errno));
            exit(SOCKET_ERROR);
        }
        LOG(FATAL, "server: creates socket successfully");
        // 2.1、定义并填充struct sockadder_in结构体，里面存放的是套接字的网络信息（ip, port, 结构体类型名）
        struct sockaddr_in localsockaddr;             // 在用户栈空间定义的一个变量
        bzero(&localsockaddr, sizeof(localsockaddr)); // 清空内容，数据全为0
        localsockaddr.sin_family = AF_INET;           // 结构的类型，套接字绑定的时候，用它检测是否类型一致
        localsockaddr.sin_port = htons(_port);        // 端口号，端口号要经过网络传输给对面，所以主机序列->网络序列
        // 1、字符串风格的点分十进制ip序列转4字节IP
        // 2、主机序列转网络序列
        // in_addr_t inet_addr(const char *cp); //可以直接完成1 & 2
        // localsockaddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // uint32_t类型
        // 可以不用我们指定ip，因为一个主机有很多ip（公网、内网等），如果绑定某个ip之后可能导致通过其他ip发送来的信息无法接收，
        // 处理方法就是ip指定为0，可以通过这台主机所有ip+port访问到该服务，但是写0又不太优雅，可以使用INADDR_ANY
        // localsockaddr.sin_addr.s_addr = 0;
        localsockaddr.sin_addr.s_addr = INADDR_ANY;

        // 2.2、套接字与sockadder_in（网络信息：ip、port）绑定
        int ret = bind(_sockfd, (struct sockaddr *)&localsockaddr, sizeof(localsockaddr));
        if (ret == -1)
        {
            LOG(FATAL, "server: bind is fatal, errno:%d, errmsg:%s", errno, strerror(errno));
            exit(BIND_ERROR);
        }

        LOG(INFO, "server: bind is successful!");
    }

    void startServer()
    {
        // 服务器一旦启动就是永远运行，除非有人将它关闭
        _isrunning = true;
        while (true)
        {
            // 1、接受发送者的消息
            char buf[1024] = {0};
            struct sockaddr_in srcaddr;
            socklen_t len = sizeof(srcaddr);
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
            // sockfd表示从哪个文件中接收，buf输出型参数，保持接受的数据，len表示期望接受的长度，flags表示
            // src_addr输出型参数，保存发送者信息，方便后期回复，addrlen表示长度
            // 返回值表示实际接受的字节个数
            ssize_t ret = recvfrom(_sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&srcaddr, &len); //0表示阻塞等待
            if (ret > 0)
            {
                //cout << "[" << inet_ntoa(srcaddr.sin_addr) << "]" << "[" << ntohs(srcaddr.sin_port) << "]" << "receive is:" << buf << endl;
                LOG(INFO, "%s:%d,recvmsg:%s", inet_ntoa(srcaddr.sin_addr), ntohs(srcaddr.sin_port), buf);

                //2、使用回调函数将消息回复给每个人
                _funcspread(_sockfd, srcaddr, buf);
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd; // 套接字文件描述符
    // string _ip;      // ip地址（可以不需要，解释见上面）
    uint16_t _port;  // 端口号
    bool _isrunning; // 服务器是否启动

    //回调函数，用来将服务器接收的消息发送给其他所有用户
    func_spread_t _funcspread;
};
